package com.core.hibernate;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;

import com.core.hibernate.exceptionevent.ExceptionEvent;
import com.core.managers.SessionFactoryManager;
import com.core.protocols.IHibernateDelegate;
import com.core.protocols.IHibernateFetchRequestDelegate;
import com.core.protocols.IKVObject;
import com.core.utils.HQLConnection;
import com.core.utils.HibernateParameter;
import com.model.KeyValue;

public class HibernateFetchRequest
        implements IHibernateDelegate {

	private IHibernateFetchRequestDelegate delegate                  = null;
	private Class <?>                      entityClass               = null;
	private String                         predicate                 = null;
	private String                         selectionCriteria         = null;
	private Object                         userInfo                  = null;
	private Set <String>                   expandEntities            = null;
	private Set <HQLConnection>            expandEntitiesConnections = null;
	private static final String            EXCEPTION_SESSION_IS_NULL = "Session is null";



	public Set <HQLConnection> getExpandEntitiesConnections () {

		return expandEntitiesConnections;
	}



	public void setExpandEntitiesConnections ( Set <HQLConnection> expandEntitiesConnections ) {

		this.expandEntitiesConnections = expandEntitiesConnections;
	}



	public Set <String> getExpandEntities () {

		return expandEntities;
	}



	public void setExpandEntities ( Set <String> expandEntities ) {

		this.expandEntities = expandEntities;
	}



	public void setUserInfo ( Object userInfo ) {

		this.userInfo = userInfo;
	}



	public Object userInfo () {

		return this.userInfo;
	}



	public Class <?> entityClass () {

		return this.entityClass;
	}



	public String predicate () {

		return predicate;
	}



	public String getSelectionCriteria () {

		return selectionCriteria;
	}



	public void setSelectionCriteria ( String value ) {

		this.selectionCriteria = value;
	}



	public IHibernateFetchRequestDelegate getDelegate () {

		return delegate;
	}



	public void setDelegate ( IHibernateFetchRequestDelegate delegate ) {

		this.delegate = delegate;
	}



	private String hqlDeleteScript () {

		String entityTableName = SessionFactoryManager.sharedInstance().annotatedClassTableName(entityClass);
		if ( entityTableName == null ) {
			return null;
		}


		StringBuilder sb = new StringBuilder();

		sb.append(String.format("delete from %s", entityTableName));
		if ( predicate != null ) {
			sb.append(String.format(" %s", predicate));

		}
		return sb.toString();

	}



	private void sendDataToDelegate ( List <?> data,
	                                  Exception exception ) {

		if ( delegate == null ) {
			return;
		}

		delegate.hibernateFetchResponse(this, data, exception);

	}



	public void performDelete ( Class <?> entityClass,
	                            String predicate ) {

		Exception exception = null;

		this.entityClass = entityClass;
		this.predicate = predicate;

		Session session = SessionFactoryManager.sharedInstance().openSession();
		if ( session == null ) {
			exception = new Exception(EXCEPTION_SESSION_IS_NULL);
			this.sendDataToDelegate(null, exception);
			return;
		}


		try {

			String hqlDeleteScript = this.hqlDeleteScript();
			if ( hqlDeleteScript == null ) {

				exception = new Exception("Delete script is empty");
				this.sendDataToDelegate(null, exception);

			} else {


				Transaction transaction = session.beginTransaction();

				try {

					/*
					 * execute
					 */
					Query query = session.createQuery(hqlDeleteScript);

					query.executeUpdate();

					transaction.commit();

					query = null;

				} catch ( RuntimeException rte ) {
					ExceptionHandler.handleException(rte, new ExceptionEvent(ExceptionEvent.PERFORMDELETE));

					if ( transaction != null ) {
						if ( transaction.isActive() ) {
							try {
								transaction.rollback();
							} catch ( Exception e ) {
								ExceptionHandler.handleException(e, new ExceptionEvent(ExceptionEvent.PERFORMDELETE));
							}
						}
					}
					transaction = null;
				}
			}
		} finally {
			session.close();
			session = null;
			this.sendDataToDelegate(null, exception);
		}

	}



	public String hqlSelectScript () {

		String entityTableName = SessionFactoryManager.sharedInstance().annotatedClassTableName(entityClass);
		if ( entityTableName == null ) {
			return null;
		}

		boolean hasExpandedEntities = (this.expandEntities != null);
		if ( hasExpandedEntities ) {
			hasExpandedEntities &= (this.expandEntities.size() > 0);
		}

		boolean hasExpandedEntitiesConnections = (this.expandEntitiesConnections != null);
		if ( hasExpandedEntitiesConnections ) {
			hasExpandedEntitiesConnections &= (this.expandEntitiesConnections.size() > 0);
		}

		StringBuilder sb = new StringBuilder();

		if ( this.selectionCriteria != null ) {
			sb.append(String.format("select %s", this.selectionCriteria));
		} else {
			// if ( hasExpandedEntities ) {
			sb.append(String.format("select %s", entityTableName));
			// }
		}

		sb.append(String.format(" from %s as %s", entityTableName, entityTableName));

		if ( hasExpandedEntities ) {
			for ( String expandedEntity : this.expandEntities ) {
				sb.append(String.format(" left join %s.%s as %s ", entityTableName, expandedEntity, expandedEntity));
			}
		}


		if ( hasExpandedEntitiesConnections ) {
			for ( HQLConnection connection : this.expandEntitiesConnections ) {
				sb.append(String.format(" join %s as %s", connection.getEntity(), connection.getAlias()));
			}
			sb.append(" where ");
			for ( HQLConnection expandedEntityConnection : this.expandEntitiesConnections ) {
				sb.append(String.format(" %s", expandedEntityConnection.getConnection()));
			}
		}

		if ( predicate != null ) {
			sb.append(String.format(" %s", predicate));

		}

		return sb.toString();

	}



	public List <?> performFetch ( String hqlSelectScript,
	                               List <?> entities ) {

		SessionFactoryManager sessionFactoryManager = SessionFactoryManager.sharedInstance();
		if ( sessionFactoryManager == null ) {
			return null;
		}


		Exception exception = null;

		if ( hqlSelectScript == null ) {
			exception = new Exception("hqlSelectScript error");
			this.sendDataToDelegate(null, exception);
			return null;
		}


		Session session = sessionFactoryManager.openSession();
		if ( session == null ) {
			return null;
		}


		List <?> result = null;
		try {

			Transaction transaction = null;
			Query query = null;
			try {

				transaction = session.beginTransaction();

				query = session.createQuery(hqlSelectScript);

				result = query.list();

				transaction.commit();

			} catch ( RuntimeException rtException ) {
				ExceptionHandler.handleException(rtException, new ExceptionEvent(ExceptionEvent.PERFORMFETCH));

				if ( transaction != null ) {
					if ( transaction.isActive() ) {
						try {
							transaction.rollback();
						} catch ( Exception e ) {
							ExceptionHandler.handleException(e, new ExceptionEvent(ExceptionEvent.PERFORMFETCH));
						}
					}
				}

			} finally {

			}

		} finally {
			session.close();
		}

		return result;
	}



	private void applyParams ( Query query,
	                           Set <HibernateParameter> params ) {

		if ( query == null ) {
			return;
		}

		if ( params == null ) {
			return;
		}

		for ( HibernateParameter param : params ) {
			query.setParameter(param.getParamName(), param.getParamValue());
		}
	}



	public void performFetch ( Class <?> entityClass,
	                           String predicate,
	                           Set <HibernateParameter> params ) {

		this.entityClass = entityClass;
		this.predicate = predicate;

		SessionFactoryManager sessionFactoryManager = SessionFactoryManager.sharedInstance();
		if ( sessionFactoryManager == null ) {
			return;
		}


		Exception exception = null;

		String sbQuery = this.hqlSelectScript();

		if ( sbQuery == null ) {
			exception = new Exception("hqlSelectScript error");
			this.sendDataToDelegate(null, exception);
			return;
		}


		Session session = sessionFactoryManager.openSession();
		if ( session == null ) {
			return;
		}


		try {

			Transaction transaction = null;
			Query query = null;
			List <?> result = null;
			try {

				transaction = session.beginTransaction();

				query = session.createQuery(sbQuery);
				this.applyParams(query, params);

				result = query.list();

				transaction.commit();

			} catch ( RuntimeException rtException ) {
				ExceptionHandler.handleException(rtException, new ExceptionEvent(ExceptionEvent.PERFORMFETCH));

				if ( transaction != null ) {
					if ( transaction.isActive() ) {
						try {
							transaction.rollback();
						} catch ( Exception e ) {
							ExceptionHandler.handleException(e, new ExceptionEvent(ExceptionEvent.PERFORMFETCH));
						}
					}
				}

			} finally {
				this.sendDataToDelegate(result, exception);
			}

		} finally {
			session.close();
		}

	}



	public static IKVObject objectByUNID ( Class <?> entityClass,
	                                       Integer unid,
	                                       Session externalSession ) {

		IKVObject result = null;
		if ( unid == null ) {
			return result;
		}

		if ( entityClass == null ) {
			return result;
		}

		boolean hasExternalSession = (externalSession != null);

		Session session = null;
		if ( !hasExternalSession ) {
			session = SessionFactoryManager.sharedInstance().openSession();
		} else {
			session = externalSession;
		}

		try {

			result = (IKVObject) session.get(entityClass, unid);

		} catch ( Exception e ) {

			ExceptionHandler.handleException(e, new ExceptionEvent(ExceptionEvent.OBJECTBYUNID));
			result = null;

		} finally {
			if ( !hasExternalSession ) {
				session.close();
			}
		}

		return result;

	}



	public static IKVObject mergeEntityByIDWithTemplate ( Integer id,
	                                                      IKVObject template )
	        throws Exception {

		IKVObject result = null;
		if ( template == null ) {
			return result;
		}

		if ( id == null ) {
			return result;
		}

		Exception exception = null;
		Session session = SessionFactoryManager.sharedInstance().openSession();
		if ( session == null ) {
			exception = new Exception(EXCEPTION_SESSION_IS_NULL);
			throw exception;
		}

		try {
			Transaction transaction = session.beginTransaction();
			if ( transaction == null ) {
				exception = new Exception("Transaction is null");
				throw exception;
			}

			try {
				result = (IKVObject) session.get(template.getClass(), id);

				template.assignTo(result);

				session.merge(result);

				transaction.commit();


			} catch ( RuntimeException runtimeException ) {

				ExceptionHandler.handleException(runtimeException, new ExceptionEvent(ExceptionEvent.MERGEOBJECT));
				if ( transaction != null ) {
					if ( transaction.isActive() ) {
						try {
							transaction.rollback();
						} catch ( Exception rollbackException ) {
							ExceptionHandler.handleException(rollbackException, new ExceptionEvent(ExceptionEvent.MERGEOBJECT));
						}
					}
				}
			}

		} finally {
			session.close();
			session = null;
		}


		return result;
	}



	public static IKVObject setCustomPropertyValue ( Class <?> objectClass,
	                                                 Integer id,
	                                                 KeyValue propertyValue )
	        throws Exception {

		IKVObject result = null;
		if ( objectClass == null ) {
			return result;
		}

		if ( id == null ) {
			return result;
		}

		Exception exception = null;
		Session session = SessionFactoryManager.sharedInstance().openSession();
		if ( session == null ) {
			exception = new Exception(EXCEPTION_SESSION_IS_NULL);
			throw exception;
		}

		try {
			Transaction transaction = session.beginTransaction();
			if ( transaction == null ) {
				exception = new Exception("Transaction is null");
				throw exception;
			}

			try {
				result = (IKVObject) session.get(objectClass, id);

				/**
				 * reflection
				 */

				Class <?>[] parametersType = new Class[1];
				parametersType[0] = String.class;
				Method customMethod = objectClass.getMethod(propertyValue.getKey(), parametersType);
				customMethod.invoke(result, propertyValue.getValue());


				session.merge(result);

				transaction.commit();


			} catch ( RuntimeException runtimeException ) {

				ExceptionHandler.handleException(runtimeException, new ExceptionEvent(ExceptionEvent.CUSTOMPROPERTYVALUE));

				if ( transaction != null ) {
					if ( transaction.isActive() ) {
						try {
							transaction.rollback();
						} catch ( Exception rollbackException ) {
							ExceptionHandler.handleException(rollbackException, new ExceptionEvent(ExceptionEvent.CUSTOMPROPERTYVALUE));
						}
					}
				}
			}

		} finally {
			session.close();
			session = null;
		}


		return result;
	}



	public static IKVObject createEntityWithTemplate ( IKVObject template,
	                                                   Session externalSession )
	        throws Exception {

		IKVObject result = null;
		if ( template == null ) {
			return result;
		}

		Exception exception = null;


		final boolean hasExternalSession = (externalSession != null);
		Session session = null;
		if ( hasExternalSession ) {
			session = externalSession;
		} else {
			session = SessionFactoryManager.sharedInstance().openSession();
		}
		if ( session == null ) {
			exception = new Exception(EXCEPTION_SESSION_IS_NULL);
			throw exception;
		}

		try {
			Transaction transaction = session.beginTransaction();
			if ( transaction == null ) {
				exception = new Exception("Transaction is null");
				throw exception;
			}

			try {
				result = template.getClass().newInstance();

				template.assignTo(result);

				session.saveOrUpdate(result);

				transaction.commit();


			} catch ( RuntimeException runtimeException ) {

				ExceptionHandler.handleException(runtimeException, new ExceptionEvent(ExceptionEvent.TEMPLATEEVENT));

				if ( transaction != null ) {
					if ( transaction.isActive() ) {
						try {
							transaction.rollback();
						} catch ( Exception rollbackException ) {
							ExceptionHandler.handleException(rollbackException, new ExceptionEvent(ExceptionEvent.TEMPLATEEVENT));
						}
					}
				}
			}

		} finally {
			if ( !hasExternalSession ) {
				session.close();
				session = null;
			}
		}


		return result;
	}
}
